﻿using System;
using System.Collections.Generic;
using AlgorytmyGenetyczne.Operatory;
using AlgorytmyGenetyczne.Wyjatki;
using Funkcje;

namespace AlgorytmyGenetyczne.Algorytmy
{
    public class PAG : IAlgorytmGenetyczny
    {
        public long WielkoscPopulacji { get; set; }
        public List<AbstractKodowanie> Kodowanie { get; private set; }
        public IFunkcja Funkcja { get; set; }
        public IOperator Selekcja { get; set; }
        public IOperator Mutacja { get; set; }
        public IOperator Krzyzowanie { get; set; }
        public Wezly wezly;
        public Rozwiazanie Rozwiazanie;
        public double PrawdopodobienstwoMutacji
        {
            get
            {
                return ((Mutacja)Mutacja).Prawdopodobienstwo;
            }
            set
            {
                ((Mutacja)Mutacja).Prawdopodobienstwo = value;
            }
        }
        public IWarunekStopu WarunekStopu { get; set; }
        public double PrawdopodobienstwoKrzyzowania
        {
            get
            {
                return ((Krzyzowanie)Krzyzowanie).Prawdopodobienstwo;
            }
            set
            {
                ((Krzyzowanie)Krzyzowanie).Prawdopodobienstwo = value;
            }
        }


        public PAG(List<AbstractKodowanie> kodowanie)
        {
            Kodowanie = kodowanie;
            //TODO przestawianie selekcji;
            //Selekcja = new SelekcjaTurniejowa();
            Selekcja = new SelekcjaRuletki();

            Mutacja = new Mutacja();
            Krzyzowanie = new Krzyzowanie();
            wezly = new Wezly();
            wezly.wczytaj("wezly.txt");
            
        }

        public Rozwiazanie ewolucja()
        {
            Populacja populacja = inicjalizacja(WielkoscPopulacji);

            try
            {
                while (WarunekStopu.sprawdz())
                {
                    populacja = krokEwolucji(populacja);
                }
            }
            catch (OsiagnietoZbieznoscException)
            { }

            populacja.Osobnicy.Sort((o1, o2) => o2.Ocena.CompareTo(o1.Ocena));
            double[] argumenty = dekoduj(populacja.Osobnicy[0].Genotyp);
            Rozwiazanie rozwiazanie = new Rozwiazanie(argumenty, 0);
            this.Rozwiazanie = rozwiazanie;
            return rozwiazanie;
        }

        public Populacja inicjalizacja(long wielkoscPopulacji)
        {
            WielkoscPopulacji = wielkoscPopulacji;
            return wygenerujPopulacje();
        }

        public Populacja krokEwolucji(Populacja populacja)
        {
            ocena(populacja);
            Populacja wyselekcjonowane = selekcja(populacja);
            Populacja skrzyzowane = krzyzowanie(wyselekcjonowane);
            return Mutacja.wykonaj(skrzyzowane);
        }

        protected Populacja wygenerujPopulacje()
        {
            Populacja populacja = new Populacja();
            Osobnik osobnik;
            Random random = new Random();

            for (int i = 0; i < WielkoscPopulacji; i++)
            {
                osobnik = new Osobnik();
                osobnik.Genotyp = Kodowanie[0].koduj(liczbaWDziedzinie(random.NextDouble(), 0));
                for (int k = 1; k < Kodowanie.Count; k++)
                    osobnik.Genotyp = osobnik.Genotyp + Kodowanie[k].koduj(liczbaWDziedzinie(random.NextDouble(), k));

                populacja.Osobnicy.Add(osobnik);
            }

            return populacja;
        }

        private double liczbaWDziedzinie(double liczbaLosowa, int k)
        {
            return (liczbaLosowa * Kodowanie[k].DlugoscDziedziny) + Kodowanie[k].PoczatekDziedziny;
        }

        protected Populacja selekcja(Populacja populacja)
        {
            return Selekcja.wykonaj(populacja);
        }

        protected Populacja krzyzowanie(Populacja populacja)
        {
            Krzyzowanie.wykonaj(populacja);
            return populacja;
        }

        public Populacja ocena(Populacja populacja)
        {
            foreach (Osobnik osobnik in populacja.Osobnicy)
            {
                double blad = 0.0;
                double[] argumenty = dekoduj(osobnik.Genotyp);
                double a, b, c, d, e;
                a = argumenty[0];
                b = argumenty[1];
                c = argumenty[2];
                d = argumenty[3];
                e = argumenty[4];
                foreach (var wezel in wezly.ListaWezlow)
                {
                    double x1 = wezel.Argument;
                    double wynik = a*Math.Exp(-b*x1)*c*Math.Sin(d*x1)+e*x1+3;
                    blad += Math.Pow(wezel.Wartosc - wynik, 2);
                }
                osobnik.Ocena = -blad;
            }
            przeskalujOceneDlaPopulacji(populacja);
            return populacja;
        }

        private double[] dekoduj(Genotyp genotyp)
        {
            int iloscZmiennych = Kodowanie.Count;
            double[] wartosci = new double[iloscZmiennych];
            int startIndex = 0;
            int endIndex = -1;
            for (int i = 0; i < iloscZmiennych; i++)
            {
                endIndex += Kodowanie[i].IloscBitow;
                wartosci[i] = (double)Kodowanie[i].dekoduj(genotyp.fragmentGenotypu(startIndex, endIndex));
                startIndex = endIndex + 1;

            }
            return wartosci;
        }

        #region Metody pomocnicze dla liczenia funkcji przystosowania

        private void przeskalujOceneDlaPopulacji(Populacja populacja)
        {
            const double dolnaGranicaZakresu = 0.0;
            const double gornaGranicaZakresu = 100.0;

            double minimalnaOcena = double.MaxValue;
            double maksymalnaOcena = double.MinValue;
            foreach (Osobnik osobnik in populacja.Osobnicy)
            {
                if (osobnik.Ocena < minimalnaOcena)
                {
                    minimalnaOcena = osobnik.Ocena;
                }
                if (osobnik.Ocena > maksymalnaOcena)
                {
                    maksymalnaOcena = osobnik.Ocena;
                }
            }

            if (minimalnaOcena == maksymalnaOcena)
            {
                //Historia.zbierajStatystyki(populacja);
                throw new OsiagnietoZbieznoscException("Minimalna ocena jest taka sama jak maksymalna. Przedział jest pusty. Wszyscy osobnicy są tak samo dobrzy.");
            }
            //double przesuniecie = Math.Abs(minimalnaOcena);
            foreach (Osobnik osobnik in populacja.Osobnicy)
            {
                //osobnik.Ocena += przesuniecie;
                osobnik.Ocena = gornaGranicaZakresu * (osobnik.Ocena - minimalnaOcena) / (maksymalnaOcena - minimalnaOcena) +
                                dolnaGranicaZakresu;
            }



        }

        [Obsolete("Metoda nie jest używana. Nie działa tak jak powinna")]
        private void obcinanieOcenyNaPodstawieOdchylenia(Populacja populacja)
        {
            double srednia = sredniaWartoscPrzystosowaniaPopulacji(populacja);
            double odchylenie = odcylenieStandardowePopulacji(populacja, srednia);
            foreach (Osobnik osobnik in populacja.Osobnicy)
            {
                //osobnik.Ocena = osobnik.Ocena + (srednia - WspolczynnikSkalowaniaOceny * odchylenie);
            }
        }

        private double odcylenieStandardowePopulacji(Populacja populacja, double sredniaWartoscPrzystosowaniaPopulacji)
        {
            double suma = 0;
            int liczbaOsobnikow = populacja.Osobnicy.Count;
            for (int i = 0; i < liczbaOsobnikow; i++)
            {
                suma += Math.Pow(populacja.Osobnicy[i].Ocena - sredniaWartoscPrzystosowaniaPopulacji, 2);
            }
            return Math.Sqrt(suma / liczbaOsobnikow);
        }

        private double sredniaWartoscPrzystosowaniaPopulacji(Populacja populacja)
        {
            double suma = 0;
            int liczbaOsobnikow = populacja.Osobnicy.Count;
            for (int i = 0; i < liczbaOsobnikow; i++)
            {
                suma += populacja.Osobnicy[i].Ocena;
            }

            return suma / liczbaOsobnikow;
        }
        #endregion
    }
}